﻿using Unity.Mathematics;
using UnityEngine;

namespace Mars.GpuInstance
{
    public static class LodUtil
    {
        //获取lodGroup的世界空间尺寸
        public static float GetWorldSpaceSize(LODGroup lodGroup)
        {
            return GetWorldSpaceScale(lodGroup.transform) * lodGroup.size;
        }
        
        //获取transform的世界空间尺寸
        public static float GetWorldSpaceScale(Transform transform)
        {
            return GetWorldSpaceScale(transform.lossyScale);
        }
        
        //获取3个缩放轴中最大的一个
        public static float GetWorldSpaceScale(Vector3 lossyScale)
        {
            float largestAxis = Mathf.Abs(lossyScale.x);
            largestAxis = Mathf.Max(largestAxis, Mathf.Abs(lossyScale.y));
            largestAxis = Mathf.Max(largestAxis, Mathf.Abs(lossyScale.z));
            return largestAxis;
        }
        
        //根据相机距离计算相对高度
        public static float DistanceToRelativeHeight(Camera camera, float distance, float size)
        {
            return DistanceToRelativeHeight(distance, size, camera.fieldOfView, camera.orthographic, camera.orthographicSize);
        }
        
        //根据相机距离计算相对高度
        public static float DistanceToRelativeHeight(float distance, float size, float fieldOfView, bool orthographic = false, float orthographicSize = 5f)
        {
            if (orthographic)
            {
                return size * 0.5f / orthographicSize;
            }

            var halfAngle = Mathf.Tan(Mathf.Deg2Rad * fieldOfView * 0.5f);
            var relativeHeight = size * 0.5f / (distance * halfAngle);
            return relativeHeight;
        }
        
        //根据相机距离计算相对高度
        public static float GetRelativeHeight(LODGroup lodGroup, Camera camera)
        {
            var distance = (lodGroup.transform.TransformPoint(lodGroup.localReferencePoint) - camera.transform.position).magnitude;
            return DistanceToRelativeHeight(camera, (distance / QualitySettings.lodBias), GetWorldSpaceSize(lodGroup));
        }

        //获取屏幕占比
        public static float GetScreenRelativeTransitionHeight(float distance, float fieldOfView, int pixelHeight, float size, Vector3 lossyScale, float lodBias)
        {
            var worldSize = GetWorldSpaceScale(lossyScale) * size;
            var relativeHeight = DistanceToRelativeHeight(distance / lodBias, worldSize, fieldOfView);
            return relativeHeight / (float)pixelHeight;
        }
        
        //获取当前lod
        public static int GetCurrentLOD(LOD[] lods, int maxLOD, float relativeHeight)
        {
            var lodIndex = maxLOD;

            for (var i = 0; i < lods.Length; i++)
            {
                var lod = lods[i];

                if (relativeHeight >= lod.screenRelativeTransitionHeight)
                {
                    lodIndex = i;
                    break;
                }
            }

            return lodIndex;
        }
        
        //获取当前lod
        public static int GetCurrentLOD(Camera cam, LODGroup lodGroup)
        {
            var inv_SceneViewCamHeight = 1.0f / (cam.pixelHeight);

            var lods = lodGroup.GetLODs();
            for (int lodIDX = 0; lodIDX < lods.Length; lodIDX++)
            {
                var lod = lods[lodIDX];
                var renderers = lod.renderers;
                for (int renderIDX = 0; renderIDX < renderers.Length; renderIDX++)
                {
                    var renderer = renderers[renderIDX];
                    var heightInScreen = GetHeightInScreen(cam, renderer);
                    var ratioInScren = heightInScreen * inv_SceneViewCamHeight;
                    if (ratioInScren > lod.screenRelativeTransitionHeight)
                    {
                        return lodIDX;
                    }
                }
            }

            return -1;
        }
        
        //计算渲染器在屏幕上的高度
        public static float GetHeightInScreen(Camera cam, Renderer renderer)
        {
            var min = renderer.bounds.min;
            var max = renderer.bounds.max;
            // F = Front
            var FTL = new Vector3(min.x, max.y, min.z);
            var FTR = new Vector3(max.x, max.y, min.z);
            var FBR = new Vector3(max.x, min.y, min.z);
            var FBL = new Vector3(min.x, min.y, min.z);

            // Back
            var BTL = new Vector3(min.x, max.y, max.z);
            var BTR = new Vector3(max.x, max.y, max.z);
            var BBR = new Vector3(max.x, min.y, max.z);
            var BBL = new Vector3(min.x, min.y, max.z);

            // to screen space pos
            FTL = cam.WorldToScreenPoint(FTL);
            FTR = cam.WorldToScreenPoint(FTR);
            FBR = cam.WorldToScreenPoint(FBR);
            FBL = cam.WorldToScreenPoint(FBL);

            BTL = cam.WorldToScreenPoint(BTL);
            BTR = cam.WorldToScreenPoint(BTR);
            BBR = cam.WorldToScreenPoint(BBR);
            BBL = cam.WorldToScreenPoint(BBL);

            var maxY = FTL.y;
            maxY = Mathf.Max(FTR.y, maxY);
            maxY = Mathf.Max(FBR.y, maxY);
            maxY = Mathf.Max(FBL.y, maxY);

            maxY = Mathf.Max(BTL.y, maxY);
            maxY = Mathf.Max(BTR.y, maxY);
            maxY = Mathf.Max(BBR.y, maxY);
            maxY = Mathf.Max(BBL.y, maxY);

            var minY = FTL.y;
            minY = Mathf.Min(FTR.y, minY);
            minY = Mathf.Min(FBR.y, minY);
            minY = Mathf.Min(FBL.y, minY);

            minY = Mathf.Min(BTL.y, minY);
            minY = Mathf.Min(BTR.y, minY);
            minY = Mathf.Min(BBR.y, minY);
            minY = Mathf.Min(BBL.y, minY);

            return maxY - minY;
        }
        
        public static int GetTriangleCount(float distance, float2 size, float fov, float aspect, float triPerPixel, float2 resolution)
        {
            var relativeArea = LodUtil.GetRelativeArea(distance, size, fov, aspect);
            var pixelCount = relativeArea * resolution.x * resolution.y;
            return (int)(pixelCount / triPerPixel);
        }
        
        public static float GetRelativeArea(float distance, float2 size, float fov, float aspect)
        {
            var relativeHeight = LodUtil.DistanceToRelativeHeight(distance, size.y, fov);
            var relativeWidth = relativeHeight * (size.x / size.y / aspect);
            return relativeHeight * relativeWidth;
        }
    
        public static float GetDistanceByArea(float2 size, float fov, float aspect, float screenArea)
        {
            // area = height * width
            //      = height^2 * aspect * (size.x/size.y);
            var relativeHeight = Mathf.Pow(screenArea / (size.x / size.y / aspect), 0.5f);
            var distance = RelativeHeightToDistance(relativeHeight, size.y, fov);
            return distance;
        }

        public static float RelativeHeightToDistance(float relativeHeight, float size, float fieldOfView)
        {
            var halfAngle = Mathf.Tan(Mathf.Deg2Rad * fieldOfView * 0.5f);
            var distance = size * 0.5f / (relativeHeight * halfAngle);
            return distance;
        }
    }
}